use std::env;
use std::io::Error;
use std::sync::{Arc, Mutex, RwLock};
use std::time::Duration;

use dotenvy::dotenv;
use redis::{Client, Commands};
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use tracing::{Level, log};
use tracing_subscriber::{FmtSubscriber, Layer};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

pub use api::*;
pub use database::*;
pub use entity::*;
pub use http::*;

/// 项目库文件

mod api;
mod database;
mod entity;
mod http;

// 在 Rust 中，尤其是在构建 Web 服务或网络服务时，AppState 结构体经常被用作一个容器，用来存储和管理应用的全局状态或资源。
// 这些资源或状态可以是数据库连接、缓存实例、配置选项、身份验证信息、日志记录器或其他在整个应用中共享或频繁使用的对象。
// AppState实现数据共享 sea-orm 参考： https://github.com/SeaQL/sea-orm/blob/master/examples/axum_example/api/src/lib.rs
// AppState实现数据共享 axum 参考： https://docs.rs/axum/latest/axum/#sharing-state-with-handlers
/*
任意时刻，其只允许一个线程访问某些数据。为了访问互斥器中的数据，线程首先需要通过获取互斥器的 锁（lock）来表明其希望访问数据。
锁是一个作为互斥器一部分的数据结构，它记录谁有数据的排他访问权。因此，我们描述互斥器为通过锁系统 保护（guarding）其数据。
互斥器以难以使用著称，因为你不得不记住：
    在使用数据之前尝试获取锁。
    处理完被互斥器所保护的数据之后，必须解锁数据，这样其他线程才能够获取锁。
*/
#[derive(Clone, Debug)]
pub struct AppState {
    // 关于 Arc 和 Mutex 的使用，可以参考： https://rustwiki.org/zh-CN/book/ch16-00-concurrency.html
    // Mutex 是一个互斥器，它允许一个线程获取互斥器的锁，以独占的方式访问数据。相当于 Java 中的 synchronized 关键字。
    pub db_pool: Arc<Mutex<Database>>,
    // RwLock 是读写锁，允许多个线程同时读取数据，但只允许一个线程写入数据。
    pub mysql: Arc<RwLock<DatabaseConnection>>,
    pub redis: Arc<Client>,
    // pub config: AppConfig,
}

// 初始化系统环境
pub async fn init_system() -> Result<String, Error> {
    // 初始化日志
    init_log().await.unwrap();

    // 初始化数据库连接
    init_mysql().await;

    // 初始化 redis 连接
    init_redis().await.expect("TODO: panic message");

    Ok("system init success".to_string())
}

// 初始化日志
pub async fn init_log() -> Result<String, Error> {
    let log_file = std::fs::File::create("log.txt").unwrap();

    /// 推荐使用这一方式
    // 开始配置一个 `fmt` subscriber
    let subscriber = tracing_subscriber::fmt()
        // 使用更紧凑、更简短的日志格式
        .compact()
        // 显示源代码文件路径
        .with_file(true)
        .with_writer(log_file)
        // 显示源代码行号
        .with_line_number(true)
        // 显示记录事件的线程 ID
        .with_thread_ids(true)
        // 不显示事件的目标（模块路径）
        .with_target(false)
        // 所有级别高于 TRACE 的 spans 事件（例如，debug、info、warn 等）都将写入 stdout
        .with_max_level(Level::TRACE)
        // 结束配置并创建 subscriber
        .finish();
    // 使用该订阅服务器处理在此时间点之后发出的跟踪
    // 在程序的剩余时间里，此订阅者将在所有线程中用作默认值，类似于记录器在 log crate 中的工作方式
    tracing::subscriber::set_global_default(subscriber)
        .expect("setting default subscriber failed");
    Ok("log init success".to_string())
}

/// sea_orm 官方网站： https://www.sea-ql.org/SeaORM/docs/install-and-config/connection/  此处为连接配置
// 初始化数据库连接池
pub async fn init_mysql() -> DatabaseConnection {
    /// 此处部分配置参考 sea_orm 官方示例： https://github.com/SeaQL/sea-orm/blob/master/examples/axum_example/api/src/lib.rs
    // 设置日志级别
    env::set_var("RUST_LOG", "debug");

    // 加载根目录下的 .env 文件配置到环境变量，而不是当前目录
    dotenv().ok();

    // env::vars().for_each(|x| println!("{:?}", x));

    let db_url = env::var("DATABASE_URL").expect("DATABASE_URL is not set in .env file");
    println!("Database URL: {}", db_url);

    // 通过数据库连接地址创建连接池配置对象
    let mut opt = ConnectOptions::new(db_url);

    // 配置连接池参数
    opt.max_connections(100)
        .min_connections(5)
        .connect_timeout(Duration::from_secs(8))
        .acquire_timeout(Duration::from_secs(8))
        .idle_timeout(Duration::from_secs(8))
        .max_lifetime(Duration::from_secs(8))
        // 是否打印SQL执行日志
        .sqlx_logging(true)
        // 如果开启SQL日志、对应设置SQL日志级别
        .sqlx_logging_level(log::LevelFilter::Info)
        // 设置默认连接的数据库
        .set_schema_search_path("blog_cloud");

    // 开始连接数据库
    let db = Database::connect(opt).await.expect("Failed to connect to database");

    // 测试数据库连接是否成功
    let result = db.ping().await.is_ok();
    println!("Database connection test result: {}", result);

    return db;
}


/// redis 官方网站： https://docs.rs/redis/0.26.0/redis/  此处为连接配置
// redis 数据库连接示例
async fn init_redis() -> redis::RedisResult<Arc<Client>> {
    // 连接 redis  注意这里的密码
    let client = redis::Client::open("redis://:479368@127.0.0.1:6379/")?;
    // 获取一个连接
    let mut conn = client.get_connection()?;
    // con.auth("479368").await?; // 再次提供密码
    // 扔掉结果，只要确保它不会失败
    let _ : () = conn.set("龙茶清欢", 2024)?;
    Ok(Arc::new(client))
}


/// 日志配置参考： https://tokio.rs/tokio/topics/tracing
/// 日志配置参考： https://course.rs/logs/tracing-logger.html
/// 日志配置参考： https://crates.io/crates/tracing
fn config_tracing() {
    let log_file = std::fs::File::create("log.txt").unwrap();

    /// 推荐使用这一方式
    // 开始配置一个 `fmt` subscriber
    let subscriber = tracing_subscriber::fmt()
        // 使用更紧凑、更简短的日志格式
        .compact()
        // 显示源代码文件路径
        .with_file(true)
        .with_writer(log_file)
        // 显示源代码行号
        .with_line_number(true)
        // 显示记录事件的线程 ID
        .with_thread_ids(true)
        // 不显示事件的目标（模块路径）
        .with_target(false)
        // 所有级别高于 TRACE 的 spans 事件（例如，debug、info、warn 等）都将写入 stdout
        .with_max_level(Level::TRACE)
        // 结束配置并创建 subscriber
        .finish();
    // 使用该订阅服务器处理在此时间点之后发出的跟踪
    // 在程序的剩余时间里，此订阅者将在所有线程中用作默认值，类似于记录器在 log crate 中的工作方式
    tracing::subscriber::set_global_default(subscriber)
        .expect("setting default subscriber failed");

    /*
        在上面的示例中，我们使用默认配置配置了FmtSubscriber。但是，跟踪订阅者还提供了许多配置FmtSubscriber行为的方法，
        例如自定义输出格式，在日志中包含其他信息（如线程ID或源代码位置），以及将日志写入stdout以外的其他位置。
    */

    let log_file = std::fs::File::create("log.txt").unwrap();
    // 构建并配置一个 `FmtSubscriber`.
    let subscriber = FmtSubscriber::builder()
        // 使用更紧凑、更简短的日志格式
        .compact()
        // 显示源代码文件路径
        .with_file(true)
        .with_writer(log_file)
        // 显示源代码行号
        .with_line_number(true)
        // 显示记录事件的线程 ID
        .with_thread_ids(true)
        // 不显示事件的目标（模块路径）
        .with_target(false)
        // 所有级别高于 TRACE 的 spans 事件（例如，debug、info、warn 等）都将写入 stdout
        .with_max_level(Level::TRACE)
        // 结束配置并创建 subscriber
        .finish();
    // 使用该订阅服务器处理在此时间点之后发出的跟踪
    // 在程序的剩余时间里，此订阅者将在所有线程中用作默认值，类似于记录器在 log crate 中的工作方式
    tracing::subscriber::set_global_default(subscriber)
        .expect("setting default subscriber failed");
}